using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;


namespace pong1
{
    /// <summary>
    /// This is a game component that implements IUpdateable.
    /// </summary>
    public class PongBall : Microsoft.Xna.Framework.DrawableGameComponent
    {
        Vector2 position;           // Current location of ball = top left corner
        Vector2 velocity;           // increments by which the paddle can move
        Texture2D ballTexture2D;    // Image used for the ball
        SpriteBatch spriteBatch;    // Stores reference to spritebatch retrieved from Services object
        String strEventReceiver;    // Event receiver name of the ball [CONFIGURATION VARIABLE]
        String strBallName;         // name of the ball for loading texture [CONFIGURATION VARIABLE]
        EventCollection eventCollection;    // Stores reference to pending events list
        String[] eventsForThisGameObjectArray;  // Stores a list of events that this object polls for
        bool bIsBallReset;                  // Stores whether the ball has been reset
        float fBallResetAccumulator;        // Whenever the ball resets, it stays still for MAX_RESET_BALL_TIME before starting to move. This accumulator stores the time that the ball stays stationary.
        Random rndBallVelocity;     // Used to randomise ball velocity after every collision (this should be done by Physics)
        int maxVariation;           // Value used to limit variation from 0 to 'maxVariation' when randomising velocity 

        public PongBall(Game game)
            : base(game)
        {
            strEventReceiver = "ball";
            strBallName = "ball";
            maxVariation = Resources.MAX_VARIATION_BALL_VELOCITY;
            rndBallVelocity = new Random(); 
            eventCollection = (EventCollection)Game.Services.GetService(typeof(EventCollection));
            velocity = new Vector2(Resources.BALL_VELOCITY_X + rndBallVelocity.Next(0 , maxVariation), Resources.BALL_VELOCITY_Y + rndBallVelocity.Next(0, maxVariation));
            bIsBallReset = false;
            fBallResetAccumulator = 0;
        }

        /// <summary>
        /// Allows the game component to perform any initialization it needs to before starting
        /// to run.  This is where it can query for any required services and load content.
        /// </summary>
        public override void Initialize()
        {
            eventCollection.RegisterForEvent("collisiondetectorball", strEventReceiver );    // register for message from collisionDetector
            ballTexture2D = Game.Content.Load<Texture2D>(strBallName);
            base.Initialize(); 
            
        }

        /// <summary>
        /// Gets references to the spriteBatch, eventCollection and set of events it's listening for
        /// </summary>
        protected override void LoadContent()
        {
            spriteBatch = (SpriteBatch)Game.Services.GetService(typeof(SpriteBatch));

            ResetBallPosition();
            String eventsForThisBall;
            eventsForThisBall = eventCollection.GetEventsForReceiver(strEventReceiver);
            eventsForThisGameObjectArray = eventsForThisBall.Split(',');
            base.LoadContent();
        }

        /// <summary>
        /// Resets ball position to center of screen.
        /// </summary>
        private void ResetBallPosition()
        {
            position.X = GraphicsDevice.Viewport.Width / 2 - ballTexture2D.Width / 2;
            position.Y = GraphicsDevice.Viewport.Height / 2 - ballTexture2D.Height / 2; ;
            bIsBallReset = true;
            fBallResetAccumulator = 0;

            int x = (int)Resources.BALL_VELOCITY_X + rndBallVelocity.Next(0, maxVariation);
            int y = (int)Resources.BALL_VELOCITY_Y + rndBallVelocity.Next(0, maxVariation);

            velocity = new Vector2(x, y);
        }

        /// <summary>
        /// Allows the game component to update itself.
        /// Updates position of ball, processes ball related events, for example collisions
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime)
        {

            if ((bIsBallReset == true) && fBallResetAccumulator < Resources.MAX_RESET_BALL_TIME)  // Give a 'MAX_RESET_BALL_TIME' second pause after ball is reset before moving the ball
            {
                fBallResetAccumulator += (float)gameTime.ElapsedGameTime.TotalSeconds;
            }
            else
            {
                bIsBallReset = false;
                putUpdateTrajectoryOnEventCollection();

                // First check if ball has collided with any wall
                // If ball collides with top or bottom wall reverse velocity
                // Else, update the score and reset the ball

                position += velocity;
                if ((((int)position.X + ballTexture2D.Width) > GraphicsDevice.Viewport.Width) || ((int)position.X <= 0))
                {
                    // Ball has gone offscreen. Inform the scoreboard by sending message. Play a sound, then reset ball position.
                    if (((int)position.X <= 0))     // Ball went off left side, increment score of right paddle
                    {
                        putIncrementScoreOnEventCollection(Resources.PADDLESIDE.RIGHTPADDLE);
                    }
                    else                    // Ball went off Right side, increment score of left paddle
                    {
                        putIncrementScoreOnEventCollection(Resources.PADDLESIDE.LEFTPADDLE);
                    }
                    putPlaysoundOnEventCollection("balloffscreen");
                    ResetBallPosition();
                }
                else if ((((int)position.Y + ballTexture2D.Height) > GraphicsDevice.Viewport.Height) || ((int)position.Y <= 0)) // Ball has hit top or bottom of screen
                {
                    velocity.Y *= -1;

                    putUpdateTrajectoryOnEventCollection();
                    putPlaysoundOnEventCollection("wallhit");
                }

                // Process events for which ball is listening. This will process any collisions
                if (!eventCollection.IsEmpty())
                {
                    Event e;
                    for (int j = 0; j < eventsForThisGameObjectArray.Length; j++)       // For each event this ball is listening for, retrieve and process the event
                    {
                        e = eventCollection.RetrieveEvent(eventsForThisGameObjectArray[j]); 

                        if (e != null)
                        {
                            processEvent(e);
                        }
                    }

                }
                // Place final ball position on eventCollection
                putPositionOnEventCollection();

                // Place ball velocity on eventCollection
                putVelocityOnEventCollection();
            }

            base.Update(gameTime);
        }

        /// <summary>
        /// The event processor for all events handled by PongBall
        /// 
        /// </summary>
        /// <param name="e"></param>
        private void processEvent(Event e)
        {

            switch (e.EventName)
            {
                case "collisiondetectorball":        // Find out what the ball has collided with, and send a msg to the physics engine, then physics will send a msg to the ball telling it it's new velocity
                    velocity.X *= -1;

                    int xoverlap = Convert.ToInt32(e.EventArguments[1].ArgumentValue), yoverlap = Convert.ToInt32(e.EventArguments[2].ArgumentValue);

                    // Move the ball out of the paddle to prevent 'zigzagging' from large overlap between ball and paddle
                    if (velocity.X < 0)
                    {
                        position.X = position.X - (ballTexture2D.Width + Resources.BALL_VELOCITY_X + Resources.MAX_VARIATION_BALL_VELOCITY + 10);   
                    }
                    else
                    {
                        position.X = position.X + (ballTexture2D.Width + Resources.BALL_VELOCITY_X + Resources.MAX_VARIATION_BALL_VELOCITY + 10);
                    }

                    // Ask physics to calculate the trajectory of the ball, send msg to audio to playsound
                    putUpdateTrajectoryOnEventCollection();
                    putPlaysoundOnEventCollection(e.EventArguments[0].ArgumentValue);
                    break;
                default:
                    break;
            }
        }


        
        /// <summary>
        /// Creates event telling which side#s score to increment and places it in eventCollection
        /// </summary>
        private void putIncrementScoreOnEventCollection(Resources.PADDLESIDE p)
        {
            Event e1 = new Event();
            e1.EventName = "updatescore";
            e1.EventArguments[0].ArgumentName = "paddleside";
            e1.EventArguments[0].ArgumentType = "String";
            
            if(p == Resources.PADDLESIDE.LEFTPADDLE)
                e1.EventArguments[0].ArgumentValue = "left";
            else
                e1.EventArguments[0].ArgumentValue = "right";

            eventCollection.Add(e1);
        }

        

        /// <summary>
        /// Creates event containing ball position and places it in eventCollection
        /// </summary>
        private void putPositionOnEventCollection()
        {
            eventCollection.RetrieveEvent("ballposition");  // Remove old event if it's on the eventCollection

            Event e1 = new Event();
            e1.EventName = "ballposition";
            e1.EventArguments[0].ArgumentName  = "x";
            e1.EventArguments[0].ArgumentType = "int";
            e1.EventArguments[0].ArgumentValue = position.X.ToString();

            e1.EventArguments[1].ArgumentName  = "y";
            e1.EventArguments[1].ArgumentType = "int";
            e1.EventArguments[1].ArgumentValue = position.Y.ToString();

            e1.EventArguments[2].ArgumentName  = "width";
            e1.EventArguments[2].ArgumentType = "int";
            e1.EventArguments[2].ArgumentValue = ballTexture2D.Width.ToString();

            e1.EventArguments[3].ArgumentName = "height";
            e1.EventArguments[3].ArgumentType = "int";
            e1.EventArguments[3].ArgumentValue = ballTexture2D.Height.ToString();

            eventCollection.Add(e1);

        }

        /// <summary>
        /// Creates event containing ball velocity and places it in eventCollection
        /// </summary>
        private void putVelocityOnEventCollection()
        {
            eventCollection.RetrieveEvent(strEventReceiver + "velocity");   // Remove the old such event if it's still on the EventCollection

            Event e2 = new Event();
            e2.EventName = strEventReceiver + "velocity";
            e2.EventArguments[0].ArgumentName = "x";
            e2.EventArguments[0].ArgumentType = "int";
            e2.EventArguments[0].ArgumentValue = position.X.ToString();

            e2.EventArguments[1].ArgumentName = "y";
            e2.EventArguments[1].ArgumentType = "int";
            e2.EventArguments[1].ArgumentValue = position.Y.ToString();

            e2.EventArguments[2].ArgumentName = "velocityx";
            e2.EventArguments[2].ArgumentType = "int";
            e2.EventArguments[2].ArgumentValue = velocity.X.ToString();

            e2.EventArguments[3].ArgumentName = "velocityy";
            e2.EventArguments[3].ArgumentType = "int";
            e2.EventArguments[3].ArgumentValue = velocity.Y.ToString();

            eventCollection.Add(e2);
        }

        /// <summary>
        /// Creates event telling Physics to calculate new velocity and places it in eventCollection
        /// </summary>
        private void putUpdateTrajectoryOnEventCollection()
        {
            eventCollection.RetrieveEvent("updatetrajectory");  // Remove old event if it's on the eventCollection

            Event e1 = new Event();
            e1.EventName = "updatetrajectory";
            e1.EventArguments[0].ArgumentName = "x";
            e1.EventArguments[0].ArgumentType = "int";
            e1.EventArguments[0].ArgumentValue = position.X.ToString();

            e1.EventArguments[1].ArgumentName = "y";
            e1.EventArguments[1].ArgumentType = "int";
            e1.EventArguments[1].ArgumentValue = position.Y.ToString();

            e1.EventArguments[2].ArgumentName = "velocityx";
            e1.EventArguments[2].ArgumentType = "int";
            e1.EventArguments[2].ArgumentValue = velocity.X.ToString();

            e1.EventArguments[3].ArgumentName = "velocityy";
            e1.EventArguments[3].ArgumentType = "int";
            e1.EventArguments[3].ArgumentValue = velocity.Y.ToString();

            e1.EventArguments[4].ArgumentName = "screenwidth";
            e1.EventArguments[4].ArgumentType = "int";
            e1.EventArguments[4].ArgumentValue = GraphicsDevice.Viewport.Width.ToString();

            eventCollection.Add(e1);
       
        }
        
        /// <summary>
        /// Creates event telling Audio to play sound and places it in eventCollection
        /// </summary>
        private void putPlaysoundOnEventCollection(String soundSource)
        {
            if (Resources.IS_SOUND_ON == false)
                return;

            eventCollection.RetrieveEvent("playsound");  // Remove old event if it's on the eventCollection

            Event e1 = new Event();
            e1.EventName = "playsound";
            e1.EventArguments[0].ArgumentName  = "soundsource";
            e1.EventArguments[0].ArgumentType = "string";
            e1.EventArguments[0].ArgumentValue = soundSource;
            eventCollection.Add(e1);

        }
       
        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Draw(GameTime gameTime)
        {

            spriteBatch.Begin();
            spriteBatch.Draw(ballTexture2D, position, Color.White);
            spriteBatch.End();

            base.Draw(gameTime);
        }

    }
}
